#!/usr/bin/env groovy

// Load utils.
/**
 * Performs the checkout step for drake (cloning into WORKSPACE/'src') and
 * drake-ci (cloning into WORKSPACE/'ci').
 *
 * @param ciSha the commit SHA or branch name to use for drake-ci
 * @param drakeSha the commit SHA or branch name to use for drake; if none is
 *                 given, uses the current branch or pull request
 * @return the scmVars object from the drake checkout
 */
def checkout(String ciSha = 'main', String drakeSha = null) {
  def scmVars = null
  retry(4) {
    // N.B. The userRemoteConfigs in the first case are crucially used for
    // production builds in order to allow pushing to additional remote
    // branches (e.g., nightly_release) beyond the one being cloned (master).
    // The second case below (`checkout scm`) does not specify such an option,
    // however, it is useful for checkout of the specific merge commit created
    // by Jenkins for pull requests as they build after a merge with master
    // (for this call, we have no access to that commit SHA).
    if (drakeSha) {
      scmVars = checkout([$class: 'GitSCM',
        branches: [[name: drakeSha]],
        extensions: [[$class: 'AuthorInChangelog'],
          [$class: 'CloneOption', honorRefspec: true, noTags: true],
          [$class: 'RelativeTargetDirectory', relativeTargetDir: 'src'],
          [$class: 'LocalBranch', localBranch: 'master']],
        userRemoteConfigs: [[
          credentialsId: 'ad794d10-9bc8-4a7a-a2f3-998af802cab0',
          name: 'origin',
          refspec: '+refs/heads/*:refs/remotes/origin/* ' +
            '+refs/pull/*:refs/remotes/origin/pr/*',
          url: 'git@github.com:RobotLocomotion/drake.git']]])
    }
    else {
      dir("${env.WORKSPACE}/src") {
        scmVars = checkout scm
      }
    }
  }
  retry(4) {
    checkout([$class: 'GitSCM',
      branches: [[name: ciSha]],
      extensions: [[$class: 'AuthorInChangelog'],
        [$class: 'CloneOption', honorRefspec: true, noTags: true],
        [$class: 'RelativeTargetDirectory', relativeTargetDir: 'ci'],
        [$class: 'LocalBranch', localBranch: 'main']],
      userRemoteConfigs: [[
        credentialsId: 'ad794d10-9bc8-4a7a-a2f3-998af802cab0',
        name: 'origin',
        refspec: '+refs/heads/*:refs/remotes/origin/* ' +
          '+refs/pull/*:refs/remotes/origin/pr/*',
        url: 'git@github.com:RobotLocomotion/drake-ci.git']]])
  }
  return scmVars
}

/**
 * Performs the main build step by calling into a drake-ci driver script
 * with the necessary credentials and environment variables.
 *
 * @param scmVars the scmVars object from drake (obtained via checkout)
 * @param stagingReleaseVersion for staging jobs, the value of the environment
 *                              variable DRAKE_VERSION used by drake-ci
 */
def doMainBuild(Map scmVars, String stagingReleaseVersion = null) {
  if (env.JOB_NAME.contains("cache-server-health-check")) {
    echo "Checking the cache server:"
    try {
      sh "${env.WORKSPACE}/ci/cache_server/health_check.bash"
    }
    catch(exc) {
      currentBuild.result = 'FAILURE'
    }
  }
  else {
    def credentials = [
      sshUserPrivateKey(credentialsId: 'ad794d10-9bc8-4a7a-a2f3-998af802cab0',
        keyFileVariable: 'SSH_PRIVATE_KEY_FILE')
    ]
    if (!env.JOB_NAME.contains("experimental")) {
      // Use Docker credentials for production jobs only.
      credentials += string(credentialsId: 'e21b9517-8aa7-419e-8f25-19cd42e10f68',
        variable: 'DOCKER_USERNAME')
      credentials += file(credentialsId: '912dd413-d419-4760-b7ab-c132ab9e7c5e',
        variable: 'DOCKER_PASSWORD_FILE')
    }
    withCredentials(credentials) {
      def environment = ["GIT_COMMIT=${scmVars.GIT_COMMIT}"]
      if (stagingReleaseVersion) {
        environment += "DRAKE_VERSION=${stagingReleaseVersion}"
      }
      withEnv(environment) {
        sh "${env.WORKSPACE}/ci/ctest_driver_script_wrapper.bash"
      }
    }
  }
}

/**
 * Sets the build result from the file written out by drake-ci.
 */
def checkBuildResult() {
  if (fileExists('RESULT')) {
    currentBuild.result = readFile 'RESULT'
  }
}

/**
 * Sends an email to Drake developers when a build fails or is unstable.
 */
def emailFailureResults() {
  if (currentBuild.result == 'FAILURE' ||
      currentBuild.result == 'UNSTABLE') {
    def subject = 'Build failed in Jenkins'
    if (currentBuild.result == 'UNSTABLE') {
      subject = 'Jenkins build is unstable'
    }
    emailext (
      subject: "${subject}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
      mimeType: 'text/html',
      body: """\
For details, see:
<ul>
  <li>Changes in this revision: ${env.BUILD_URL}changes</li>
  <li>Changelog for this job: ${env.BUILD_URL}display/redirect?page=changes</li>
  <li>Console log: ${env.BUILD_URL}console</li>
</ul>
""",
      to: '$DEFAULT_RECIPIENTS',
    )
  }
}

/**
 * Deletes the workspace and tmp directories.
 */
def cleanWorkspace() {
  dir(env.WORKSPACE) {
    deleteDir()
  }
  dir("${env.WORKSPACE}@tmp") {
    deleteDir()
  }
}

/**
 * Provides links to CDash to view the results of the build.
 */
def addCDashBadge() {
  if (fileExists('CDASH')) {
    def cDashUrl = readFile 'CDASH'
    addBadge icon: '/userContent/cdash.png',
      link: cDashUrl, text: 'View in CDash'
    addSummary icon: '/userContent/cdash.png',
      link: cDashUrl, text: 'View in CDash'
  }
}

/**
 * Extracts the node label from the job name.
 *
 * @return the node label
 */
def getNodeLabel() {
  def pattern = ~/^((linux(-arm)?|mac-arm)-[A-Za-z]+(-unprovisioned)?).*/
  def match = env.JOB_NAME =~ pattern

  if (match.find()) {
    return match.group(1)
  }
  else {
    return null
  }
}


// Check if CI should be deferred for this pull request based on the label.
// CHANGE_ID should always be defined in this file, as it corresponds to
// pull requests, but we should check anyways.
if(env.CHANGE_ID) {
  if (pullRequest.labels.contains('status: defer ci')) {
    currentBuild.result == 'ABORTED'
    throw new Exception(
      "This pull request is labeled 'defer ci'. " +
      "This label will need to be removed in order for CI to pass " +
      "before merging."
    )
  }
}

node(getNodeLabel()) {
  // Define job properties. Other Jenkinsfiles which do not correspond to
  // multibranch pipelines (i.e., for non-experimental jobs) should inherit
  // properties from the original job definitions rather than calling
  // properties() here.
  def props = [
    parameters([
      string(name: 'ciSha', defaultValue: 'main',
        description: 'Commit SHA or branch name. ' +
          'For pull requests, enter branch name <code>pr/1234/head</code> ' +
          'or <code>pr/1234/merge</code> for pull request #1234. ' +
          'Defaults to <code>main</code>.'),
      ]
    ),
    buildDiscarder(
      logRotator(
        daysToKeepStr: '90',
        artifactDaysToKeepStr: '90'
      )
    ),
    disableConcurrentBuilds(
      abortPrevious: true
    )
  ]
  properties(props)

  stage('test') {
    timeout(600) {
      ansiColor('xterm') {
        timestamps {
          try {
            cleanWorkspace()
            dir(env.WORKSPACE) {
              // Use the CI branch parameter for checkout (defaults to main).
              def scmVars = checkout(params.ciSha)
              doMainBuild(scmVars)
            }
          } finally {
            try {
              checkBuildResult()
              addCDashBadge()
            } finally {
              cleanWorkspace()
            }
          }
        }
      }
    }
  }
}
